package jcommand;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
import jcommand.exception.DuplicateCommandKeyException;
import jcommand.exception.CommandNotFoundException;

/**
 * This class is the highest level and the developers can store a set of command
 * with unique command key. Therefore, the developer can make use of the command
 * key to run the command <br>
 * <br>
 * Example: Using command key to execute the command <br>
 * <code>
 * <br>Command c1 = new Command("SayHelloToGrace");
 * <br>Command c2 = new Command("ShutdownComputer");
 * <br>
 * <br>CommandSet commandSet = new CommandSet(); 
 * <br>commandSet.add(c1); 
 * <br>commandSet.add(c2);
 * <br> 
 * <br>commandSet.runCommand("SayHelloToGrace");
 * <br></code>
 * 
 * @author Kenneth Lo
 * @see jcommand.Command
 * @since 1.0.0
 */
public class CommandSet {

	/**
	 * All the commands is stored into a hashtable and setting the command key
	 * as the hasttable key.
	 * 
	 * @since 1.0.0
	 */
	private Hashtable<String, Command> h = new Hashtable<String, Command>(10);

	/**
	 * All the commands is stored into a vector for having the order prop.
	 * 
	 * @since 1.0.0
	 */
	private Vector<Command> v = new Vector<Command>(10, 10);

	/**
	 * Appends the specified command to the end of this command set.
	 * 
	 * @param command
	 *            the specified command to the end of this command set
	 * @see jcommand.Command
	 * @throws DuplicateCommandKeyException
	 *             if the command key already exist in the command set
	 * @since 1.0.0
	 */
	public void addCommand(Command command) {
		if (h.containsKey(command.getCommandKey()))
			throw new DuplicateCommandKeyException(command.getCommandKey());

		h.put(command.getCommandKey(), command);
		v.add(command);
	}

	/**
	 * Remove the specified command with specific command key.
	 * 
	 * @param commandKey
	 *            the commandKey refer to the command
	 * @see jcommand.Command
	 * @since 1.0.0
	 */
	public void removeCommand(String commandKey) {
		v.removeElement(h.get(commandKey.toLowerCase()));
		h.remove(commandKey.toLowerCase());
	}

	/**
	 * Return all the command key which inserted to the command set.
	 * 
	 * @return string array whcih included all the command keys
	 * @since 1.0.0
	 */
	public String[] getCommandKeys() {
		String[] keys = new String[v.size()];

		for (int i = 0; i < keys.length; i++)
			keys[i] = v.elementAt(i).getCommandKey();

		return keys;
	}

	/**
	 * Execute the command with specific command key.
	 * 
	 * @param commandKey
	 *            the commandKey refer to the command
	 * @throws IOException
	 *             if the command is running a command line statement and the
	 *             statement throw exception.
	 * @since 1.0.0
	 */
	public void runCommand(String commandKey) throws IOException {

		if (h.isEmpty())
			throw new CommandNotFoundException();

		try {
			h.get(commandKey.toLowerCase()).runCommand();
		} catch (NullPointerException e) {
			throw new CommandNotFoundException();
		}
	}

	/**
	 * Execute the command with specific command key. The command key will
	 * execute if and only if the check command key is matched.
	 * 
	 * @param commandKey
	 *            the commandKey refer to the command
	 * @param checkCommandKey
	 *            conidtion commandKey, this command will only execute if the
	 *            input command key is matched with the checking command key
	 *            which define when creating the object
	 * @throws IOException
	 *             if the command is running a command line statement and the
	 *             statement throw exception.
	 * @see jcommand.Command#getCheckCommandKey()
	 * @since 1.0.0
	 */
	public void runCommand(String commandKey, String checkCommandKey)
			throws IOException {

		if (h.isEmpty())
			throw new CommandNotFoundException();

		String checkKey = h.get(commandKey.toLowerCase()).getCheckCommandKey();

		if (checkKey != null)
			if (!checkKey.equalsIgnoreCase(checkCommandKey))
				return;

		try {
			h.get(commandKey.toLowerCase()).runCommand();
		} catch (NullPointerException e) {
			throw new CommandNotFoundException();
		}
	}

	/**
	 * Execute all the command in the command set. The command is execute with
	 * the order of inset. First add first execute.
	 * 
	 * @throws IOException
	 *             if ont of the commands is running a command line statement
	 *             and the statement throw exception.
	 * @since 1.0.0
	 */
	public void runAllCommands() throws IOException {
		runAllCommands(0);
	}

	/**
	 * Execute all the command in the command set. The command is execute with
	 * the order of inset. First add first execute. After running each command,
	 * the system will wait a period of time.
	 * 
	 * @param time
	 *            the waiting (in ms) time between each exection of the command
	 * @throws IOException
	 *             if ont of the commands is running a command line statement
	 *             and the statement throw exception.
	 * @since 1.0.0
	 */
	public void runAllCommands(long time) throws IOException {
		String[] commandKeys = getCommandKeys();

		for (int i = 0; i < commandKeys.length; i++) {
			runCommand(commandKeys[i]);

			try {
				Thread.sleep(time);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}

	/**
	 * Return the number of commands in this command set.
	 * 
	 * @return number of commands in this command set
	 * @since 1.0.0
	 */
	public int size() {
		return h.size();
	}

	/**
	 * Tests if this command set has no actions.
	 * 
	 * @return <code>true</code> if and only if this command set has no
	 *         actions, that is, its size is zero; <code>false</code>
	 *         otherwise.
	 * @since 1.0.0
	 */
	public boolean isEmpty() {
		return h.isEmpty();
	}
}
